#pragma once
#include "KMeansStandardV1_Util.h"
#include "Logger.h"

#include <math.h>

namespace Algorithm
{
template<unsigned int OBSERVATION_VECTOR_SIZE, unsigned int NUMBER_OF_OBSERVATIONS, unsigned int NUMBER_OF_CENTROIDS >
class KMeansStandardV1NonConcurrent
{
public:
	typedef KMeansStandardV1_Util<OBSERVATION_VECTOR_SIZE, NUMBER_OF_OBSERVATIONS, NUMBER_OF_CENTROIDS > SupportedKMeansStandardV1_Util;

	static void algorithm(const typename SupportedKMeansStandardV1_Util::TSpAlgorithmData& _algorithmData)
	{
		int loop=0;
		for(loop=0;true;loop++)
		{
			findClosestCentroid(_algorithmData);
			calculateNewCentroids(_algorithmData);
			checkConvergence(_algorithmData);
			if(_algorithmData->convergence)break;
		}
		Logger::getOStream()<<std::endl<<"Loops="<<loop;
	}

	static void findClosestCentroid(const typename SupportedKMeansStandardV1_Util::TSpAlgorithmData& _algorithmData)
	{
		const cl_float* pObservation_=(const cl_float*)_algorithmData->observations.data;
		for(int observationI=0;observationI<NUMBER_OF_OBSERVATIONS;observationI++,pObservation_+=OBSERVATION_VECTOR_SIZE)
		{
			cl_float minDist_=CL_FLT_MAX;
			cl_uint minI_=0;
			const cl_float* pCentroid_=(const cl_float*)_algorithmData->currentCentroids.data;
			for(int centroidI=0;centroidI<NUMBER_OF_CENTROIDS;centroidI++,pCentroid_+=OBSERVATION_VECTOR_SIZE)
			{
				cl_float currentDist_=calculateDistance(pObservation_,pCentroid_);
				if(currentDist_<minDist_)
				{
					minDist_=currentDist_;
					minI_=centroidI;
				}
			}
			_algorithmData->closestCentroid.data[observationI]=minI_;
		}

	}

	static void calculateNewCentroids(const typename SupportedKMeansStandardV1_Util::TSpAlgorithmData& _algorithmData)
	{
		_algorithmData->lastCentroids=_algorithmData->currentCentroids;

		static SupportedKMeansStandardV1_Util::TCentroids sumOfDalaPoints_;
		sumOfDalaPoints_.initialize((cl_float)0.0f);


		static SupportedKMeansStandardV1_Util::SupportedAlgorithmInput::WrappedArray1<cl_uint,NUMBER_OF_CENTROIDS> numberOfObservations_;
		numberOfObservations_.initialize((cl_uint)0.0f);

		const cl_float* pObservation_=(const cl_float*)_algorithmData->observations.data;
		for(int observationI=0;observationI<NUMBER_OF_OBSERVATIONS;observationI++,pObservation_+=OBSERVATION_VECTOR_SIZE)
		{
			cl_uint currentClosestCentroidI_=_algorithmData->closestCentroid.data[observationI];
			calculateSum((cl_float*)sumOfDalaPoints_.data[currentClosestCentroidI_],(const cl_float*)sumOfDalaPoints_.data[currentClosestCentroidI_],(const cl_float*) pObservation_);
			numberOfObservations_.data[currentClosestCentroidI_]++;
		}


		for(int centroidI=0;centroidI<NUMBER_OF_CENTROIDS;centroidI++)
		{
			if(numberOfObservations_.data[centroidI]>0)
			{
				calculateMultiply((cl_float*)sumOfDalaPoints_.data[centroidI],(const cl_float*)sumOfDalaPoints_.data[centroidI],
					((cl_float)1.0f)/((cl_float)numberOfObservations_.data[centroidI]));
			}
		}

		_algorithmData->currentCentroids=sumOfDalaPoints_;
	}

	static void checkConvergence(const typename SupportedKMeansStandardV1_Util::TSpAlgorithmData& _algorithmData)
	{
		_algorithmData->convergence=
			(_algorithmData->lastCentroids==_algorithmData->currentCentroids);
	}


	static cl_float calculateDistance(const cl_float* _x1,const cl_float* _x2)
	{
		cl_float returned=0;
		for(int i=0;i<OBSERVATION_VECTOR_SIZE;i++,_x1++,_x2++)
		{
			cl_float diff_=(*_x1)-(*_x2);
			returned+=diff_*diff_;
		}

		returned=sqrtf(returned);
		return returned;
	}

	static void calculateSum(cl_float* _output,const cl_float* _x1,const cl_float* _x2)
	{
		cl_float returned=0;
		for(int i=0;i<OBSERVATION_VECTOR_SIZE;i++,_output++,_x1++,_x2++)
		{
			(*_output)=(*_x1)+(*_x2);
		}

	}

	static void calculateMultiply(cl_float* _output,const cl_float* _InputVec,const cl_float& _InputScalar)
	{
		cl_float returned=0;
		for(int i=0;i<OBSERVATION_VECTOR_SIZE;i++,_output++,_InputVec++)
		{
			(*_output)=(*_InputVec)*(_InputScalar);
		}

	}
};
}//namespace Algorithm
